using System;
using System.Collections.Generic;
using System.Linq;
using AutoMapper;
using SharpArch.Core.DomainModel;
using NHibernate.Validator.Constraints;

namespace Peak.Core
{
  public class Sprint : EntityWithBusinessRules
  {
    public Sprint()
    {
      SprintStories = new List<SprintStory>();
      Burndown = new List<SprintBacklogEntry>();      
    }

    [NotNullNotEmpty]
    public virtual string Name { get; set; }
    [NotEmpty]
    public virtual string Description { get; set; }
    
    public virtual DateTime StartDate { get; set; }
    public virtual DateTime EndDate { get; set; }

    public virtual DateTime Created { get; set; }
    [NotNullNotEmpty]
    public virtual string CreatedBy { get; set; }

    private SprintStatus _status = SprintStatus.Planning;
    public virtual SprintStatus Status
    {
      get
      {
        return _status;
      }
      set
      {
        if (!RulesEnabled())
        {
          _status = value;
          return;
        }

        if(_status != value)
        {          
          if(value != SprintStatus.Finished && Project != null)
          {
            if (Project.CurrentSprint != null && Project.CurrentSprint != this)
              throw new OnlyOneOngoingSprintException();

            Project.CurrentSprint = this;
          }
          if (_status == SprintStatus.Planning && value == SprintStatus.InProgress)
          {
            InitialEffort = RemainingEffort;            
          }
          if(value == SprintStatus.Planning)
          {
            Burndown.Clear();
          }
          if (value == SprintStatus.InProgress && !AllEstimated())
          {
            throw new NotEstimatedException();
          }

          if (value == SprintStatus.Finished)
          {
            if (Project != null)
            {
              Project.CurrentSprint = null;
            }
          }

          _status = value;
        }
      }
    }
    public virtual Project Project { get; set; }

    private bool AllEstimated()
    {
      foreach (SprintStory sprintStory in SprintStories)
      {
        if (!sprintStory.AllEstimated() || sprintStory.RemainingEffort == TimeSpan.Zero)
          return false;
      }
      return true;
    }

    private TimeSpan _remainingEffort;
    public virtual TimeSpan RemainingEffort
    {
      get
      {
        return _remainingEffort;
      }
      set
      {
        if(_remainingEffort != value)
        {
          _remainingEffort = value;

          if(Status == SprintStatus.InProgress)
          {
            AddBurndownEffort(DateTime.Now, value);                                         
          }
        }
      }
    }
    public virtual TimeSpan EstimatedEffort { get; set; }
    public virtual TimeSpan InitialEffort { get; set; } 
    public virtual TimeSpan CurrentEffort { get; set; }
    [Min(0)]
    public virtual double EstimatedVelocity { get; set; }

    [Min(0)]
    public virtual double RealVelocity { get; set; }

    public virtual IList<SprintStory> SprintStories { get; set; }

    public virtual IList<SprintBacklogEntry> Burndown { get; set; }

    public virtual TimeSpan GetBurndownEffort(DateTime date)
    {
      if(date > EndDate.Date || date < StartDate.Date)  
        throw new ArgumentOutOfRangeException();

      for(int i = 0;i<Burndown.Count;i++)
      {
        if (Burndown[i].Date == date)
          return Burndown[i].Effort;
        if(Burndown[i].Date > date)
        {
          if (i == 0)
            return InitialEffort;
          else
            return Burndown[i - 1].Effort;
        }          
      }

      return Burndown[Burndown.Count-1].Effort;
    }

    public virtual void AddBurndownEffort(DateTime date, TimeSpan effort)
    {
      if(date.Date > EndDate.Date || date.Date < StartDate.Date)
        return;

      if (Burndown.Count == 0 || Burndown[Burndown.Count - 1].Date != date.Date)
      {
        if(Burndown.Count > 0 && Burndown[Burndown.Count-1].Date > date)
          throw new ArgumentException("Date is too low, there is higher value in collection");

        Burndown.Add(new SprintBacklogEntry() {Date = date.Date, Effort = effort});
      }
      else
      {
        if(Burndown.Count > 1)
        {
          Burndown[Burndown.Count - 1].Effort = effort;
        }
      }
    }

    public virtual void AddStory(Story story)
    {
      if(Status == SprintStatus.Finished)
        throw new SprintFinishedException();

      if (story.SprintStories.FirstOrDefault(f=>f.Sprint == this) != null)
        return;

      SprintStory sprintStory;
      if (story.CurrentSprintStory.Sprint == null)
      {
        sprintStory = story.CurrentSprintStory;        
      }
      else
      {
        sprintStory = new SprintStory();        
        story.AddSprintStory(sprintStory);
        CopyUnfinishedTasks(sprintStory, story.SprintStories[story.SprintStories.Count-2]);
      }
      sprintStory.Sprint = this;
      SprintStories.Add(sprintStory);
      EstimatedVelocity += sprintStory.StoryPoints;
      CurrentEffort += sprintStory.CurrentEffort;
      RemainingEffort += sprintStory.RemainingEffort;
      if (sprintStory.Status == StoryStatus.Done)
      {
        throw new StoryFinishedException();
      }
    }

    private void CopyUnfinishedTasks(SprintStory t_sprintStory, SprintStory s_sprintStory)
    {
      foreach (Task task in s_sprintStory.Tasks)
      {
        if (!task.IsDone)
        {
          Task clone = task.Clone();
          clone.Status = TaskStatus.ToDo;          
          t_sprintStory.AddTask(clone);
        }
      }
    }    

    public virtual void RemoveStory(Story story)
    {
      if (Status == SprintStatus.Finished)
        throw new SprintFinishedException();

      SprintStory sprintStory = SprintStories.FirstOrDefault(f => f.Sprint == this);
      if (sprintStory == null)
        return;

      SprintStories.Remove(sprintStory);
      sprintStory.Sprint = null;
      EstimatedVelocity -= sprintStory.StoryPoints;
      if (sprintStory.Status == StoryStatus.Done)
        RealVelocity -= sprintStory.StoryPoints;
      RemainingEffort -= sprintStory.RemainingEffort;
      CurrentEffort -= sprintStory.CurrentEffort;      
    }
  }
}